/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <assert.h>

#include "uv.h"
#include "internal.h"
#include "req-inl.h"

/* EAI_* constants. */
#if USING_VC6RT != 1
#include <winsock2.h>
#endif

int uv__getaddrinfo_translate_error(int sys_err)
{
    switch (sys_err) {
    case 0:
        return 0;
    case WSATRY_AGAIN:
        return UV_EAI_AGAIN;
    case WSAEINVAL:
        return UV_EAI_BADFLAGS;
    case WSANO_RECOVERY:
        return UV_EAI_FAIL;
    case WSAEAFNOSUPPORT:
        return UV_EAI_FAMILY;
    case WSA_NOT_ENOUGH_MEMORY:
        return UV_EAI_MEMORY;
    case WSAHOST_NOT_FOUND:
        return UV_EAI_NONAME;
    case WSATYPE_NOT_FOUND:
        return UV_EAI_SERVICE;
    case WSAESOCKTNOSUPPORT:
        return UV_EAI_SOCKTYPE;
    default:
        return uv_translate_sys_error(sys_err);
    }
}

/*
 * MinGW is missing this
 */
#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) || USING_VC6RT == 1
typedef struct addrinfoW {
    int ai_flags;
    int ai_family;
    int ai_socktype;
    int ai_protocol;
    size_t ai_addrlen;
    WCHAR* ai_canonname;
    struct sockaddr* ai_addr;
    struct addrinfoW* ai_next;
} ADDRINFOW, *PADDRINFOW;

DECLSPEC_IMPORT int WSAAPI GetAddrInfoW(const WCHAR* node,
    const WCHAR* service,
    const ADDRINFOW* hints,
    PADDRINFOW* result);

DECLSPEC_IMPORT void WSAAPI FreeAddrInfoW(PADDRINFOW pAddrInfo);
#endif

/* adjust size value to be multiple of 4. Use to keep pointer aligned */
/* Do we need different versions of this for different architectures? */
#define ALIGNED_SIZE(X) ((((X) + 3) >> 2) << 2)

static void uv__getaddrinfo_work(struct uv__work* w)
{
    uv_getaddrinfo_t* req;
    struct addrinfoW* hints;
    int err;

    req = container_of(w, uv_getaddrinfo_t, work_req);
    hints = req->addrinfow;
    req->addrinfow = NULL;
    err = GetAddrInfoW(req->node, req->service, hints, &req->addrinfow);
    req->retcode = uv__getaddrinfo_translate_error(err);
}

/*
 * Called from uv_run when complete. Call user specified callback
 * then free returned addrinfo
 * Returned addrinfo strings are converted from UTF-16 to UTF-8.
 *
 * To minimize allocation we calculate total size required,
 * and copy all structs and referenced strings into the one block.
 * Each size calculation is adjusted to avoid unaligned pointers.
 */
static void uv__getaddrinfo_done(struct uv__work* w, int status)
{
    uv_getaddrinfo_t* req;
    int addrinfo_len = 0;
    int name_len = 0;
    size_t addrinfo_struct_len = ALIGNED_SIZE(sizeof(struct addrinfo));
    struct addrinfoW* addrinfow_ptr;
    struct addrinfo* addrinfo_ptr;
    char* alloc_ptr = NULL;
    char* cur_ptr = NULL;

    req = container_of(w, uv_getaddrinfo_t, work_req);

    /* release input parameter memory */
    uv__free(req->alloc);
    req->alloc = NULL;

    if (status == UV_ECANCELED) {
        assert(req->retcode == 0);
        req->retcode = UV_EAI_CANCELED;
        goto complete;
    }

    if (req->retcode == 0) {
        /* convert addrinfoW to addrinfo */
        /* first calculate required length */
        addrinfow_ptr = req->addrinfow;
        while (addrinfow_ptr != NULL) {
            addrinfo_len += addrinfo_struct_len + ALIGNED_SIZE(addrinfow_ptr->ai_addrlen);
            if (addrinfow_ptr->ai_canonname != NULL) {
                name_len = WideCharToMultiByte(CP_UTF8,
                    0,
                    addrinfow_ptr->ai_canonname,
                    -1,
                    NULL,
                    0,
                    NULL,
                    NULL);
                if (name_len == 0) {
                    req->retcode = uv_translate_sys_error(GetLastError());
                    goto complete;
                }
                addrinfo_len += ALIGNED_SIZE(name_len);
            }
            addrinfow_ptr = addrinfow_ptr->ai_next;
        }

        /* allocate memory for addrinfo results */
        alloc_ptr = (char*)uv__malloc(addrinfo_len);

        /* do conversions */
        if (alloc_ptr != NULL) {
            cur_ptr = alloc_ptr;
            addrinfow_ptr = req->addrinfow;

            while (addrinfow_ptr != NULL) {
                /* copy addrinfo struct data */
                assert(cur_ptr + addrinfo_struct_len <= alloc_ptr + addrinfo_len);
                addrinfo_ptr = (struct addrinfo*)cur_ptr;
                addrinfo_ptr->ai_family = addrinfow_ptr->ai_family;
                addrinfo_ptr->ai_socktype = addrinfow_ptr->ai_socktype;
                addrinfo_ptr->ai_protocol = addrinfow_ptr->ai_protocol;
                addrinfo_ptr->ai_flags = addrinfow_ptr->ai_flags;
                addrinfo_ptr->ai_addrlen = addrinfow_ptr->ai_addrlen;
                addrinfo_ptr->ai_canonname = NULL;
                addrinfo_ptr->ai_addr = NULL;
                addrinfo_ptr->ai_next = NULL;

                cur_ptr += addrinfo_struct_len;

                /* copy sockaddr */
                if (addrinfo_ptr->ai_addrlen > 0) {
                    assert(cur_ptr + addrinfo_ptr->ai_addrlen <= alloc_ptr + addrinfo_len);
                    memcpy(cur_ptr, addrinfow_ptr->ai_addr, addrinfo_ptr->ai_addrlen);
                    addrinfo_ptr->ai_addr = (struct sockaddr*)cur_ptr;
                    cur_ptr += ALIGNED_SIZE(addrinfo_ptr->ai_addrlen);
                }

                /* convert canonical name to UTF-8 */
                if (addrinfow_ptr->ai_canonname != NULL) {
                    name_len = WideCharToMultiByte(CP_UTF8,
                        0,
                        addrinfow_ptr->ai_canonname,
                        -1,
                        NULL,
                        0,
                        NULL,
                        NULL);
                    assert(name_len > 0);
                    assert(cur_ptr + name_len <= alloc_ptr + addrinfo_len);
                    name_len = WideCharToMultiByte(CP_UTF8,
                        0,
                        addrinfow_ptr->ai_canonname,
                        -1,
                        cur_ptr,
                        name_len,
                        NULL,
                        NULL);
                    assert(name_len > 0);
                    addrinfo_ptr->ai_canonname = cur_ptr;
                    cur_ptr += ALIGNED_SIZE(name_len);
                }
                assert(cur_ptr <= alloc_ptr + addrinfo_len);

                /* set next ptr */
                addrinfow_ptr = addrinfow_ptr->ai_next;
                if (addrinfow_ptr != NULL) {
                    addrinfo_ptr->ai_next = (struct addrinfo*)cur_ptr;
                }
            }
            req->addrinfo = (struct addrinfo*)alloc_ptr;
        } else {
            req->retcode = UV_EAI_MEMORY;
        }
    }

    /* return memory to system */
    if (req->addrinfow != NULL) {
        FreeAddrInfoW(req->addrinfow);
        req->addrinfow = NULL;
    }

complete:
    uv__req_unregister(req->loop, req);

    /* finally do callback with converted result */
    if (req->getaddrinfo_cb)
        req->getaddrinfo_cb(req, req->retcode, req->addrinfo);
}

void uv_freeaddrinfo(struct addrinfo* ai)
{
    char* alloc_ptr = (char*)ai;

    /* release copied result memory */
    uv__free(alloc_ptr);
}

/*
 * Entry point for getaddrinfo
 * we convert the UTF-8 strings to UNICODE
 * and save the UNICODE string pointers in the req
 * We also copy hints so that caller does not need to keep memory until the
 * callback.
 * return 0 if a callback will be made
 * return error code if validation fails
 *
 * To minimize allocation we calculate total size required,
 * and copy all structs and referenced strings into the one block.
 * Each size calculation is adjusted to avoid unaligned pointers.
 */
int uv_getaddrinfo(uv_loop_t* loop,
    uv_getaddrinfo_t* req,
    uv_getaddrinfo_cb getaddrinfo_cb,
    const char* node,
    const char* service,
    const struct addrinfo* hints)
{
    int nodesize = 0;
    int servicesize = 0;
    int hintssize = 0;
    char* alloc_ptr = NULL;
    int err;

    if (req == NULL || (node == NULL && service == NULL)) {
        err = WSAEINVAL;
        goto error;
    }

    uv_req_init(loop, (uv_req_t*)req);

    req->getaddrinfo_cb = getaddrinfo_cb;
    req->addrinfo = NULL;
    req->type = UV_GETADDRINFO;
    req->loop = loop;
    req->retcode = 0;

    /* calculate required memory size for all input values */
    if (node != NULL) {
        nodesize = ALIGNED_SIZE(MultiByteToWideChar(CP_UTF8, 0, node, -1, NULL, 0) * sizeof(WCHAR));
        if (nodesize == 0) {
            err = GetLastError();
            goto error;
        }
    }

    if (service != NULL) {
        servicesize = ALIGNED_SIZE(MultiByteToWideChar(CP_UTF8,
                                       0,
                                       service,
                                       -1,
                                       NULL,
                                       0)
            * sizeof(WCHAR));
        if (servicesize == 0) {
            err = GetLastError();
            goto error;
        }
    }
    if (hints != NULL) {
        hintssize = ALIGNED_SIZE(sizeof(struct addrinfoW));
    }

    /* allocate memory for inputs, and partition it as needed */
    alloc_ptr = (char*)uv__malloc(nodesize + servicesize + hintssize);
    if (!alloc_ptr) {
        err = WSAENOBUFS;
        goto error;
    }

    /* save alloc_ptr now so we can free if error */
    req->alloc = (void*)alloc_ptr;

    /* convert node string to UTF16 into allocated memory and save pointer in */
    /* the request. */
    if (node != NULL) {
        req->node = (WCHAR*)alloc_ptr;
        if (MultiByteToWideChar(CP_UTF8,
                0,
                node,
                -1,
                (WCHAR*)alloc_ptr,
                nodesize / sizeof(WCHAR))
            == 0) {
            err = GetLastError();
            goto error;
        }
        alloc_ptr += nodesize;
    } else {
        req->node = NULL;
    }

    /* convert service string to UTF16 into allocated memory and save pointer */
    /* in the req. */
    if (service != NULL) {
        req->service = (WCHAR*)alloc_ptr;
        if (MultiByteToWideChar(CP_UTF8,
                0,
                service,
                -1,
                (WCHAR*)alloc_ptr,
                servicesize / sizeof(WCHAR))
            == 0) {
            err = GetLastError();
            goto error;
        }
        alloc_ptr += servicesize;
    } else {
        req->service = NULL;
    }

    /* copy hints to allocated memory and save pointer in req */
    if (hints != NULL) {
        req->addrinfow = (struct addrinfoW*)alloc_ptr;
        req->addrinfow->ai_family = hints->ai_family;
        req->addrinfow->ai_socktype = hints->ai_socktype;
        req->addrinfow->ai_protocol = hints->ai_protocol;
        req->addrinfow->ai_flags = hints->ai_flags;
        req->addrinfow->ai_addrlen = 0;
        req->addrinfow->ai_canonname = NULL;
        req->addrinfow->ai_addr = NULL;
        req->addrinfow->ai_next = NULL;
    } else {
        req->addrinfow = NULL;
    }

    uv__req_register(loop, req);

    if (getaddrinfo_cb) {
        uv__work_submit(loop,
            &req->work_req,
            uv__getaddrinfo_work,
            uv__getaddrinfo_done);
        return 0;
    } else {
        uv__getaddrinfo_work(&req->work_req);
        uv__getaddrinfo_done(&req->work_req, 0);
        return req->retcode;
    }

error:
    if (req != NULL) {
        uv__free(req->alloc);
        req->alloc = NULL;
    }
    return uv_translate_sys_error(err);
}
